package computation;

import java.util.ArrayList;
import java.util.HashSet;

import triangulation.DelaunayMapping;
import util.GeoVertex;
import delaunay_triangulation.Point_dt;

public class Smoothing
{
	// measures the smoothness of a vertex, introduced by Karni and Gotsman [2000]
	// takes a point and a set of points which are assumed to be it's neighbours
	public static GeoVertex geometricLaplacian(GeoVertex v, HashSet<Integer> neighbourkeys, int iter, DelaunayMapping mapping)
	{		
		// let "tmp" hold the current values of x,y,z that will become GL_v; the difference vector
		double[] tmp = new double[3]; tmp[0] = tmp[1] = tmp[2] = 0.0;
		double denom = 0.0;

		for (Integer nk: neighbourkeys)
		{
			GeoVertex p = mapping.vertices[nk];
			
			// this gets us the exact non-smoothed neighbour, thus we must get the smoothed neighbour!	
			GeoVertex v_i = p;
			if (iter > 0) v_i = p.getLOSVertex(iter - 1);
			
			double l_i = v.distance3D(v_i);
			double inverse = (1.0/l_i);
			
			double x_i = inverse * v_i.x();
			double y_i = inverse * v_i.y();
			double z_i = inverse * v_i.z();
			
			tmp[0] += x_i;
			tmp[1] += y_i;
			tmp[2] += z_i;
			
			denom += inverse;
		}

		// this GL vector represents the difference vector between v and it's new position after a Laplacian smoothing step
		double GL_x = v.x() - (tmp[0]/denom);
		double GL_y = v.y() - (tmp[1]/denom);
		double GL_z = v.z() - (tmp[2]/denom);
		Point_dt pt = new Point_dt(v.x() - GL_x, v.y() - GL_y, v.z() - GL_z);
		GeoVertex gv = new GeoVertex(pt);
		gv.neighbourkeys = v.neighbourkeys;
		gv.trianglekeys = v.trianglekeys;
		gv.id = v.id;
		return gv;
	}
	
	public static GeoVertex adaptiveSmoothing(GeoVertex src, ArrayList<GeoVertex> edge_points)
	{
		double lambda = 0.6307;
		double mu = -0.6732;
		
		double[] v = new double[3];
		v[0] = src.x();
		v[1] = src.y();
		v[2] = src.z();
		
		double[] sv = TabinStep(edge_points, v);
		double[] vm = new double[3];
		
		vm[0] = v[0] + lambda*sv[0];
		vm[1] = v[1] + lambda*sv[1];
		vm[2] = v[2] + lambda*sv[2];
		
		double[] steptwo = TabinStep(edge_points, vm);
		double[] v_prime = new double[3];
		v_prime[0] = vm[0] + mu*steptwo[0];
		v_prime[1] = vm[1] + mu*steptwo[1];
		v_prime[2] = vm[2] + mu*steptwo[2];
		
		Point_dt p = new Point_dt(v_prime[0], v_prime[1], v_prime[2]);
		GeoVertex gv = new GeoVertex(p);
		gv.neighbourkeys = src.neighbourkeys;
		gv.trianglekeys = src.trianglekeys;
		gv.id = src.id;
		return gv;
	}
	
	public static double[] TabinStep(ArrayList<GeoVertex> edge_points, double[] v)
	{
		int N = edge_points.size();
		double[] sv = new double[3];
		sv[0] = sv[1] = sv[2] = 0.0;
		
		for (GeoVertex gv: edge_points)
		{
			double dx = gv.x() - v[0];
			double dy = gv.y() - v[1];
			double dz = gv.z() - v[2];
			
			sv[0] += dx;
			sv[1] += dy;
			sv[2] += dz;
		}
		
		sv[0] /= N;
		sv[1] /= N;
		sv[2] /= N;
		return sv;
	}
	
}
